/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtCookie.h"
#include "WtContext.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtMultiTable.h"
#include "WtCollectionCmds.h"
#include "WtBasicCmds.h"

/* Create a new cookie */

Tcl_Obj *WtNewCookieObj()
{
  Tcl_Obj *obj = WtNewTableObj();
  WtTableSetStrToStr(obj, "name", "");
  WtTableSetStrToObj(obj, "values", Tcl_NewListObj(0, NULL));
  WtTableSetStrToStr(obj, "expires", "");
  WtTableSetStrToStr(obj, "domain", "");
  WtTableSetStrToStr(obj, "path", "");
  WtTableSetStrToObj(obj, "secure", Tcl_NewBooleanObj(0));
  return obj;
}

/* Get the name */

Tcl_Obj *WtCookieGetName(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  Tcl_Obj *name = WtTableGetObjFromStr(cookie, "name", NULL);
  if (!name) {
    Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
  }
  return name;
}

/* Set the name */

int WtCookieSetName(Tcl_Obj *cookie, Tcl_Obj *name, Tcl_Interp *interp)
{
  WtTableSetStrToObj(cookie, "name", name);
  return 1;
}

/* Get the value */

int WtCookieGetValue(Tcl_Obj *cookie, Tcl_Obj **value, Tcl_Interp *interp)
{
  int ret = 0;
  Tcl_Obj *key = WtNewString("values"), *list;

  *value = NULL;
  if (WtTableGetList(cookie, key, &list, interp)) {
    if (!list) {
      Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
    } else if (Tcl_ListObjIndex(interp, list, 0, value) == TCL_OK) {
      ret = 1;
    }
  }

  Tcl_DecrRefCount(key);
  return ret;
}

/* Set the value */

int WtCookieSetValue(Tcl_Obj *cookie, Tcl_Obj *value, Tcl_Interp *interp)
{
  WtTableSetStrToObj(cookie, "values",
    Tcl_NewListObj(1, &value));
  return 1;
}

/* Get all values */

int WtCookieGetAllValues(Tcl_Obj *cookie, Tcl_Obj **values, Tcl_Interp *interp)
{
  int ret = 0;
  Tcl_Obj *key = WtNewString("values");

  if (WtTableGetList(cookie, key, values, interp)) {
    if (!*values) {
      Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
    } else {
      ret = 1;
    }
  }

  Tcl_DecrRefCount(key);
  return ret;
}

/* Set all values */

int WtCookieSetAllValues(Tcl_Obj *cookie, Tcl_Obj *values, Tcl_Interp *interp)
{
  WtTableSetStrToObj(cookie, "values", values);
  return 1;
}

/* Get expiration time */

Tcl_Obj *WtCookieGetExpires(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  Tcl_Obj *expires = WtTableGetObjFromStr(cookie, "expires", NULL);
  if (!expires) {
    Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
  }
  return expires;
}

/* Set the expiration time */

int WtCookieSetExpires(Tcl_Obj *cookie, Tcl_Obj *expires, Tcl_Interp *interp)
{
  int ret = 0;
  const char *str;
  WtContext *w = WtGetAssocContext(interp);

  str = ApacheUtil_expires(w->web->apReq->pool,
    WtToString(expires), EXPIRES_COOKIE);

  if (!str) {
    Tcl_AppendResult(interp, "Expiration time string is invalid.");
  } else {
    WtTableSetStrToStr(cookie, "expires", str);
    ret = 1;
  }

  return ret;
}

int WtCookieMarkExpired(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  int ok = 0;
  Tcl_Obj *epoch = WtNewString(wtCookieEpoch);
  Tcl_IncrRefCount(epoch);
  ok = WtCookieSetExpires(cookie, epoch, interp);
  Tcl_DecrRefCount(epoch);
  return ok;
}

/* Get the domain */

Tcl_Obj *WtCookieGetDomain(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  Tcl_Obj *domain = WtTableGetObjFromStr(cookie, "domain", NULL);
  if (!domain) {
    Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
  }
  return domain;
}

/* Set the domain */

int WtCookieSetDomain(Tcl_Obj *cookie, Tcl_Obj *domain, Tcl_Interp *interp)
{
  WtTableSetStrToObj(cookie, "domain", domain);
  return 1;
}

/* Get the path */

Tcl_Obj *WtCookieGetPath(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  Tcl_Obj *path = WtTableGetObjFromStr(cookie, "path", NULL);
  if (!path) {
    Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
  }
  return path;
}

/* Set the path */

int WtCookieSetPath(Tcl_Obj *cookie, Tcl_Obj *path, Tcl_Interp *interp)
{
  WtTableSetStrToObj(cookie, "path", path);
  return 1;
}

/* Get the secure option */

Tcl_Obj *WtCookieGetSecure(Tcl_Obj *cookie, Tcl_Interp *interp)
{
  Tcl_Obj *secure = WtTableGetObjFromStr(cookie, "secure", NULL);
  if (!secure) {
    Tcl_AppendResult(interp, "Invalid cookie object.", NULL);
  }
  return secure;
}

/* Set the secure option */

int WtCookieSetSecure(Tcl_Obj *cookie, Tcl_Obj *secure, Tcl_Interp *interp)
{
  int intVal; 
  if (Tcl_GetBooleanFromObj(interp, secure, &intVal) != TCL_OK) {
    Tcl_AppendResult(interp, "Secure value must be boolean.", NULL);
    return 0;
  }
  WtTableSetStrToObj(cookie, "secure", Tcl_NewBooleanObj(intVal));
  return 1;
}

/* cookie command */

int WtCookieCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  char *subCmd;

  if (objc < 2) {
    WtCookieUsage(interp, objv[0]);
  } else {
    subCmd = WtToString(objv[1]);

    if (!strcmp(subCmd, "create")) {
      ret = WtCookieCreateCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "name")) {
      ret = WtCookieNameCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "value")) {
      ret = WtCookieValueCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "allValues")) {
      ret = WtCookieAllValuesCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "expires")) {
      ret = WtCookieExpiresCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "markExpired")) {
      ret = WtCookieMarkExpiredCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "domain")) {
      ret = WtCookieDomainCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "path")) {
      ret = WtCookiePathCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "secure")) {
      ret = WtCookieSecureCmd(clientData, interp, objc, objv);
    } else {
      WtCookieUsage(interp, objv[0]);
    }
  }

  return ret;
}

/* cookie create command */

int WtCookieCreateCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  char *argStr;
  int ret = TCL_ERROR, i, optsDone = 0, intVal, error = 0;
  Tcl_Obj *cookie = WtNewCookieObj(), *boolObj;

  Tcl_IncrRefCount(cookie);

  for (i = 2; i < objc; i++) {
    argStr = WtToString(objv[i]);
    if (!strcmp(argStr, "-expires")) {
      if (i + 1 >= objc) {
        Tcl_AppendResult(interp,
          "The \"-expires\" option requires a value.", NULL);
        error = 1;
        break;
      } else if (!WtCookieSetExpires(cookie, objv[i + 1], interp)) {
        error = 1;
        break;
      } else {
        i++;
      }
    } else if (!strcmp(argStr, "-domain")) {
      if (i + 1 >= objc) {
        Tcl_AppendResult(interp,
          "The \"-domain\" option requires a value.", NULL);
          error = 1;
          break;
      } else {
        WtCookieSetDomain(cookie, objv[i + 1], interp);
        i++;
      }
    } else if (!strcmp(argStr, "-path")) {
      if (i + 1 >= objc) {
        Tcl_AppendResult(interp, "The \"-path\" option requires a value.",
          NULL);
        error = 1;
        break;
      } else {
        WtCookieSetPath(cookie, objv[i + 1], interp);
        i++;
      }
    } else if (!strcmp(argStr, "-secure")) {
      if (i + 1 >= objc) {
        Tcl_AppendResult(interp,
          "The \"-secure\" option requires a value.", NULL);
        error = 1;
        break;
      } else if (Tcl_GetBooleanFromObj(interp, objv[2], &intVal) != TCL_OK) {
        error = 1;
        break;
      } else {
        boolObj = Tcl_NewBooleanObj(0);
        Tcl_IncrRefCount(boolObj);
        if (!WtCookieSetSecure(cookie, boolObj, interp)) {
          error = 1;
        }
        Tcl_DecrRefCount(boolObj);
        if (error) {
          break;
        } else {
          i++;
        }
      }
    } else if (!strcmp(argStr, "--")) {
      optsDone = 1;
      break;
    } else if (argStr && argStr[0] == '-') {
      Tcl_AppendResult(interp, "Option \"", argStr, "\" is invalid. ",
        NULL);
      WtCookieUsage(interp, objv[0]);
      error = 1;
      break;
    } else {
      optsDone = 1;
      break;
    }
  }

  if (!error) {
    if (i + 2 < objc) {
      Tcl_AppendResult(interp, "Extra parameters found at the end. ");
      WtCookieUsage(interp, objv[0]);
      error = 1;
    } else if (i < objc) {
      if (!WtCookieSetName(cookie, objv[i], interp)) {
        error = 1;
      } else if (i + 1 < objc) {
        if (!WtCookieSetValue(cookie, objv[i + 1], interp)) {
          error = 1;
        }
      }
    }
  }

  if (!error) {
    Tcl_SetObjResult(interp, cookie);
    ret = TCL_OK;
  }

  Tcl_DecrRefCount(cookie);

  return ret;
}

/* cookie name command */

int WtCookieNameCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *name;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (name = WtCookieGetName(cookie, interp)) {
        Tcl_SetObjResult(interp, name);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetName(cookie, objv[3], interp)) {
        if (name = WtCookieGetName(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, name);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar name\n",
      NULL);
  }

  return ret;
}

/* cookie value command */

int WtCookieValueCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *value;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (WtCookieGetValue(cookie, &value, interp)) {
        Tcl_SetObjResult(interp, value ? value : WtNewString(NULL));
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetValue(cookie, objv[3], interp)) {
        if (WtCookieGetValue(cookie, &value, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, value ? value : WtNewString(NULL));
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[2]), " ",
      " cookieVar value\n",
      NULL);
  }

  return ret;
}

/* cookie allValues command */

int WtCookieAllValuesCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *values;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (WtCookieGetAllValues(cookie, &values, interp)) {
        Tcl_SetObjResult(interp, values);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetAllValues(cookie, objv[3], interp)) {
        if (WtCookieGetAllValues(cookie, &values, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, values);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[2]), " ",
      " cookieVar values\n",
      NULL);
  }

  return ret;
}

/* cookie expires command */

int WtCookieExpiresCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *expires;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (expires = WtCookieGetExpires(cookie, interp)) {
        Tcl_SetObjResult(interp, expires);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetExpires(cookie, objv[3], interp)) {
        if (expires = WtCookieGetExpires(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, expires);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar expirationDate\n",
      NULL);
  }

  return ret;
}

/* cookie markExpired command */

int WtCookieMarkExpiredCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *expires;

  if (objc == 3) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieMarkExpired(cookie, interp)) {
        if (expires = WtCookieGetExpires(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, expires);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar",
      NULL);
  }

  return ret;
}

/* cookie domain command */

int WtCookieDomainCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *domain;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (domain = WtCookieGetDomain(cookie, interp)) {
        Tcl_SetObjResult(interp, domain);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetDomain(cookie, objv[3], interp)) {
        if (domain = WtCookieGetDomain(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, domain);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar domain\n",
      NULL);
  }

  return ret;
}

/* cookie path command */

int WtCookiePathCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *path;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (path = WtCookieGetPath(cookie, interp)) {
        Tcl_SetObjResult(interp, path);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetPath(cookie, objv[3], interp)) {
        if (path = WtCookieGetPath(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, path);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar path\n",
      NULL);
  }

  return ret;
}

/* cookie secure command */

int WtCookieSecureCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, created;
  Tcl_Obj *cookie, *secure;

  if (objc == 3) {
    if (cookie = WtCookieCmdGetVal(objv[2], interp)) {
      if (secure = WtCookieGetSecure(cookie, interp)) {
        Tcl_SetObjResult(interp, secure);
        ret = TCL_OK;
      }
    }
  } else if (objc == 4) {
    if (WtCookieCmdOwnVar(objv[2], &cookie, &created, interp)) {
      if (WtCookieSetSecure(cookie, objv[3], interp)) {
        if (secure = WtCookieGetSecure(cookie, interp)) {
          if (WtCookieCmdSetVar(objv[2], cookie, interp)) {
            Tcl_SetObjResult(interp, secure);
            ret = TCL_OK;
          }
        }
      }
      if (created) {
        Tcl_DecrRefCount(cookie);
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVal\n",
      WtToString(objv[0]), " ", WtToString(objv[1]), " ",
      " cookieVar secure\n",
      NULL);
  }

  return ret;
}

void WtCookieUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = WtToString(cmd);
  Tcl_AppendResult(interp, wtBadUsagePrefix, "\n",
    cmdStr, " create\n",
    cmdStr, " name cookieVal\n",
    cmdStr, " name cookieVar name\n",
    cmdStr, " value cookieVal\n",
    cmdStr, " value cookieVar value\n",
    cmdStr, " allValues cookieVal\n",
    cmdStr, " allValues cookieVar values\n",
    cmdStr, " expires cookieVal\n",
    cmdStr, " expires cookieVar expires\n",
    cmdStr, " domain cookieVal\n",
    cmdStr, " domain cookieVar domain\n",
    cmdStr, " path cookieVal\n",
    cmdStr, " path cookieVar path\n",
    cmdStr, " secure cookieVal\n",
    cmdStr, " secure cookieVar secure\n",
    NULL);
}

Tcl_Obj *WtCookieCmdGetVal(Tcl_Obj *val, Tcl_Interp *interp)
{
  return WtCmdGetVal(val, &WtTableType, interp);
}

int WtCookieCmdGetVar(Tcl_Obj *var, Tcl_Obj **val,
    Tcl_Interp *interp)
{
  return WtCmdGetVar(var, val, &WtTableType, interp);
}

int WtCookieCmdOwnVar(Tcl_Obj *var, Tcl_Obj **val,
  int *created, Tcl_Interp *interp)
{
  int ok = WtCmdOwnVar(var, val, &WtTableType, created, interp);
  if (ok && !*val) {
    *val = WtNewCookieObj();
    Tcl_IncrRefCount(*val);
    *created = 1;
  }
  return ok;
}

Tcl_Obj *WtCookieCmdSetVar(Tcl_Obj *var, Tcl_Obj *val, Tcl_Interp *interp)
{
  return WtCmdSetVar(var, val, interp);
}

/* Cookie collection object */

Tcl_Obj *WtNewCookieClnObj()
{
  Tcl_Obj *obj = WtNewClnObj(&WtCookieClnType, &WtCookieClnItemType);
  return obj;
}

struct Tcl_ObjType WtCookieClnType =
{
  "cookieCollection",
  WtCookieClnFree,
  WtCookieClnDup,
  WtCookieClnUpdateString,
  WtCookieClnSetFromAny
};

void WtCookieClnFree(Tcl_Obj *cln)
{
  WtClnObjFree(cln);
}

void WtCookieClnDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtClnObjDup(src, dst);
}

void WtCookieClnUpdateString(Tcl_Obj *cln)
{
  WtClnObjUpdateString(cln);
}

int WtCookieClnSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtClnObjSetFromAny(interp, obj, &WtCookieClnType);
}

WtClnItemType WtCookieClnItemType = {
  WtCookieClnCompare,
  WtCookieClnGetKey,
  WtCookieClnGetValue
};

int WtCookieClnCompare(Tcl_Obj *item, Tcl_Obj *key, int *result,
    Tcl_Interp *interp)
{
  Tcl_Obj *name = WtCookieGetName(item, interp);
  if (name) {
    *result = strcmp(WtToString(name), WtToString(key));
    return 1;
  }
  return 0;
}

int WtCookieClnGetKey(Tcl_Obj *item, Tcl_Obj **key,
    Tcl_Interp *interp)
{
  *key = WtCookieGetName(item, interp);
  if (*key) {
    return 1;
  }
  return 0;
}

int WtCookieClnGetValue(Tcl_Obj *item, Tcl_Obj **value,
    Tcl_Interp *interp)
{
  *value = item;
  return 1;
}

/* responseCookies command */

int WtResponseCookiesCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret, ok = 1;
  WtContext *w = WtGetAssocContext(interp);
  Tcl_Obj *cookie, *val;

  int status = WtClnMethodHelper(clientData, interp, objc,
    objv, &ret, &w->web->clientResponse.cookies,
    &WtCookieClnType, 0, 0);

  if (status == WT_CLN_CMD_MISSING) {

    /* No command */

    WtResponseCookiesUsage(interp, objv[0]);

  } else if (status == WT_CLN_CMD_NOT_FOUND) {

    /* deleteFromClient command */

    if (!strcmp(WtToString(objv[1]), "deleteFromClient")) {
      if (objc < 3) {
        Tcl_AppendResult(interp, wtBadUsagePrefix,
          WtToString(objv[0]), " ", WtToString(objv[1]), " ",
          " name", NULL);
      } else if (cookie = WtNewCookieObj()) {
        Tcl_IncrRefCount(cookie);

        if (!WtCookieSetName(cookie, objv[2], interp)) {
          ok = 0;
        }

        if (ok && objc > 3 && !WtCookieSetPath(
            cookie, objv[3], interp)) {
          ok = 0;
        }

        if (ok && objc > 4 && !WtCookieSetDomain(
            cookie, objv[4], interp)) {
          ok = 0;
        }

        if (ok && objc > 5 && !WtCookieSetSecure(
            cookie, objv[5], interp)) {
          ok = 0;
        }

        if (ok) {
          val = WtNewString(NULL);
          Tcl_IncrRefCount(val);
          if (WtCookieSetValue(cookie, val, interp)) {
            if (WtCookieMarkExpired(cookie, interp)) {
              if (WtOwnCookieCln(&w->web->clientResponse.cookies,
                  interp)) {
                if (WtAppendClnItem(w->web->clientResponse.cookies,
                    cookie, interp)) {
                  Tcl_SetObjResult(interp, cookie);
                  ret = TCL_OK;
                }
              }
            }
          }
          Tcl_DecrRefCount(val);
        }

        Tcl_DecrRefCount(cookie);
      }

    } else {

      /* Unknown command */

      WtResponseCookiesUsage(interp, objv[0]);

    }
  }

  return ret;
}

void WtResponseCookiesUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  Tcl_AppendResult(interp, wtBadUsagePrefix2, NULL);
  WtClnMethodAppendUsage(interp, cmd, 0);
  Tcl_AppendResult(interp, WtToString(cmd), " deleteFromClient key\n",
    NULL);
}

/* Initialize cookie commands */

void WtInitCookieCommands(Tcl_Interp *interp)
{
  WtContext *w = Tcl_GetAssocData(interp, "wt::context", NULL);

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
    "Wtcl: WtInitCookieCommands.");

  Tcl_CreateObjCommand(interp, "::wt::response::cookie",
    WtCookieCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::response::responseCookies",
    WtResponseCookiesCmd, NULL, NULL);
}

int WtConvertToCookieCln(Tcl_Obj *obj, Tcl_Interp *interp)
{
  return WtConvertToClnObj(obj, &WtCookieClnType, interp);
}

int WtOwnCookieCln(Tcl_Obj **objPtr, Tcl_Interp *interp)
{
  WtCopyOnWrite(objPtr);
  return WtConvertToCookieCln(*objPtr, interp);
}

const char *wtCookieEpoch = "Thu, 01-Jan-1970 00:00:01 GMT";
